CMU 15-213 Intro to Computer Systems Lecture 6
课程主页:http://www.cs.cmu.edu/afs/cs/academic/class/15213-f15/www/schedule.html
课程资料:https://github.com/EugeneLiu/translationCSAPP
课程视频:https://www.bilibili.com/video/av31289365/
这一讲介绍了机器级编程2:控制。
控制:条件码
条件码(隐式设置)
单比特寄存器
- CF:进位标志(无符号)
- SF:符号标志(有符号)
- ZF:零标记
- OF:溢出标志(有符号)
通过算术运算隐式设置
例如:addq Src, Dest $ \leftrightarrow t = a + b $
如果最高有效位进位,则CF置为1(无符号溢出)
如果$ t == 0 $,则ZF置为1
如果$ t <0 $,则SF置为1
如果
则OF置为1
leaq指令不会改变条件码
条件码(显式设置)
通过比较指令进行显式设置
cmpq Src2,Src1
cmpq b,类似于计算a-b而不设置
如果最高有效位进位,则CF置为1(无符号溢出)
如果$ a == b $,则ZF置为1
如果$ (a-b) <0 $,则SF置为1
如果
则OF置为1
通过测试指令进行显式设置
- testq Src2,Src1
- testq b,a类似于计算$a \& b$而不设置dest
- 根据$ \operatorname {Src} 1\& \operatorname {Src} 2 $的值设置条件码
- 使一个操作数成为掩码很有用
- 当$ a \& b == 0 $时设置ZF为1
- 当$a \& b <0 $时设置SF为1
- testq Src2,Src1
完整比较测试指令如下:
读取条件码
- SetX指令
- 根据条件码的组合,将dest的低位字节设置为0或1
- 不会更改剩余的7个字节
来看一个具体例子:
int gt (long x, long y)
{
return x > y;
}
汇编代码为
cmpq %rsi, %rdi # Compare x:y
setg %al # Set when >
movzbl %al, %eax # Zero rest of %rax
ret
变量和寄存器的对应关系为
条件分支
跳转指令
条件分支例子
考虑如下例子:
long absdiff(long x, long y)
{
long result;
if (x > y)
result = x-y;
else
result = y-x;
return result;
}
为了和汇编码格风格相近,首先将上述代码改写为goto的格式:
long absdiff_j(long x, long y)
{
long result;
int ntest = x <= y;
if (ntest) goto Else;
result = x-y;
goto Done;
Else:
result = y-x;
Done:
return result;
}
汇编码为
absdiff:
cmpq %rsi, %rdi # x:y
jle .L4
movq %rdi, %rax
subq %rsi, %rax
ret
.L4: # x <= y
movq %rsi, %rax
subq
ret
变量和寄存器的对应关系为
一般情形如下:
C语言:
val = Test ? Then_Expr : Else_Expr;
汇编码:
ntest = !Test;
if (ntest) goto Else;
val = Then_Expr;
goto Done;
Else:
val = Else_Expr;
Done:
. . .
使用条件移动
- 条件移动指令
- 指令支持:
- $\text{if (Test) Dest}\leftarrow \text{Src}$
- 在1995年后的x86处理器中受支持
- GCC尝试使用它们
- 但是,只有在已知安全的情况下为
- 指令支持:
- 为什么?
- 分支对流水线中的指令流造成很大破坏
- 条件移动不需要控制转移
C代码
val = Test ? Then_Expr : Else_Expr;
汇编代码
result = Then_Expr;
eval = Else_Expr;
nt = !Test;
if (nt) result = eval;
return result;
一个具体例子如下:
long absdiff(long x, long y)
{
long result;
if (x > y)
result = x-y;
else
result = y-x;
return result;
}
汇编码如下:
absdiff:
movq %rdi, %rax # x
subq %rsi, %rax # result = x-y
movq %rsi, %rdx
subq %rdi, %rdx # eval = y-x
cmpq %rsi, %rdi # x:y
cmovle %rdx, %rax # if <=, result = eval
ret
寄存器和变量的对应关系如下:
条件移动的坏案例
昂贵的计算
val = Test(x) ? Hard1(x) : Hard2(x);
- 计算两个值
- 仅在计算非常简单时才有意义
风险计算
val = p ? *p : 0;
- 计算两个值
- 可能会有不良影响
计算有副作用
val = x > 0 ? x*=7 : x+=3;
- 计算两个值
- 必须无副作用
循环
“Do-While”循环例子
C代码
long pcount_do(unsigned long x)
{
long result = 0;
do {
result += x & 0x1;
x >>= 1;
} while (x);
return result;
}
goto版本
long pcount_goto(unsigned long x)
{
long result = 0;
loop:
result += x & 0x1;
x >>= 1;
if(x) goto loop;
return result;
}
汇编代码
movl $0, %eax # result = 0
.L2: # loop:
movq %rdi, %rdx
andl $1, %edx # t = x & 0x1
addq %rdx, %rax # result += t
shrq %rdi # x >>= 1
jne .L2 # if (x) goto loop
rep; ret
寄存器和变量的对应关系如下:
一般的”Do-While”翻译
Do-While代码
do
Body
while (Test);
Goto版本
loop:
Body
if (Test)
goto loop
其中Body的形式如下
{ Statement1; Statement2; … Statementn; }
一般的”While”翻译
While代码
while (Test)
Body
Goto版本
goto test;
loop:
Body
test:
if (Test)
goto loop;
done:
另一种方法是将While修改为Do-While
if (!Test)
goto done;
do
Body
while(Test);
done:
Goto版本
if (!Test)
goto done;
loop:
Body
if (Test)
goto loop;
done:
一般的”For”循环翻译
For循环形式
for (Init; Test; Update )
Body
修改为While循环
Init;
while (Test) {
Body
Update;
}
Switch语句
跳转表结构
Switch分支使用了跳转表结构:
跳转表将分支的条件转换为0到n,然后补齐中间缺失的部分。
例子
C代码
long switch_eg (long x, long y, long z)
{ long w = 1;
switch(x) {
case 1: //.L3
w = y*z;
break;
case 2: //.L5
w = y/z;
/* Fall Through */
case 3: //.L9
w += z;
break;
case 5:
case 6: //.L7
w -= z;
break;
default: //.L8
w = 2;
}
return w;
}
跳转表
.section .rodata
.align 8
.L4:
.quad .L8 # x = 0
.quad .L3 # x = 1
.quad .L5 # x = 2
.quad .L9 # x = 3
.quad .L8 # x = 4
.quad .L7 # x = 5
.quad .L7 # x = 6
总结
- C控制
- if-then-else
- do-while
- while, for
- switch
- 汇编程序控制
- 条件跳跃
- 条件移动
- 间接跳转(通过跳转表)
- 编译器生成代码序列以实现更复杂的控制
- 标准技术
- 循环转换为do-‐while或jump-to-middle的形式
- 大型switch语句使用跳转表
- 稀疏的switch语句可能使用决策树(if-elseif-elseif-else)